package org.maxgroundstation.groundstation;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.SwingWorker;

import org.maxgroundstation.groundstation.comm.CommandSender;
import org.maxgroundstation.groundstation.comm.service.ServiceCommunication;
import org.maxgroundstation.groundstation.view.GroundStationFrame;
import org.maxgroundstation.world.WorldModel;


/**
 * The main ground station class.
 * @author Scott Davis
 * 
 * Copyright (C) 2010  Scott Davis
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
public class GroundStation {
	
    // Data members.
    private ControllerInputPoller inputPoller = null;
    private CommandSender commandSender = null;
    private boolean useTCPVideo = false;
    private ServiceCommunication serviceComm = null;
    private boolean cameraDriveLock;
    private boolean useLowBandwidthVideo;
    private String defaultIP;
    private List<GroundStationListener> listeners;
    private WorldModel world;
    
    /**
     * Constructor
     * @param defaultIP the default IP address.
     * @param worldMetaData the world meta data file path.
     */
    public GroundStation(String defaultIP, String worldMetaData) {
    	this.defaultIP = defaultIP;
    	cameraDriveLock = true;
    	useLowBandwidthVideo = false;
    	listeners = new ArrayList<GroundStationListener>();
    	serviceComm = new ServiceCommunication(this);
        commandSender = new CommandSender(this);
        inputPoller = new ControllerInputPoller(this);
        
        createWorldModel(worldMetaData);
    }
    
    /**
     * Create the world model in a separate thread.
     * @param worldMetaData the world meta data file path.
     */
    private void createWorldModel(String worldMetaData) {
        // Use swing worker inner class to create world model on separate thread.
        class WorldCreator extends SwingWorker<WorldModel, Object> {

            private String metaData;
            
            WorldCreator(String metaData) {
                this.metaData = metaData;
            }
            
            @Override
            protected WorldModel doInBackground() throws Exception {
                // Create world model.
                return new WorldModel(metaData);
            }
            
            @Override
            protected void done() {
                try { 
                    setWorldModel(get());
                } 
                catch (Exception ignore) {}
            }
        }
        (new WorldCreator(worldMetaData)).execute();
    }
    
    /**
     * Gets the command sender.
     * @return command sender.
     */
    public CommandSender getCommandSender() {
        return commandSender;
    }
    
    /**
     * Gets the service communication.
     * @return service communication.
     */
    public ServiceCommunication getServiceComm() {
    	return serviceComm;
    }
    
    /**
     * Gets the input poller.
     * @return input poller.
     */
    public ControllerInputPoller getInputPoller() {
        return inputPoller;
    }
    
    /**
     * Gets the camera drive lock.
     * @return true if camera is locked to drive.
     */
    public boolean getCameraDriveLock() {
    	return cameraDriveLock;
    }
    
    /**
     * Sets the camera drive lock.
     * @param cameraDriveLock true if camera is locked to drive.
     */
    public void setCameraDriveLock(boolean cameraDriveLock) {
    	this.cameraDriveLock = cameraDriveLock;
    }
    
    /**
     * Checks if ground station is using low bandwidth video.
     * @return true if using low bandwidth video.
     */
    public boolean useLowBandwidthVideo() {
    	return useLowBandwidthVideo;
    }
    
    /**
     * Sets if the ground station will be using low bandwidth video.
     * @param useLowBandwidthVideo true if will be using low bandwidth video.
     */
    public void setUseLowBandwidthVideo(boolean useLowBandwidthVideo) {
    	this.useLowBandwidthVideo = useLowBandwidthVideo;
    	commandSender.updateVideoParams();
    }
    
    /**
     * Checks if ground station is using TCP for video.
     * @return true if using TCP for video, false if using UDP for video.
     */
    public boolean useTCPVideo() {
    	return useTCPVideo;
    }
    
    /**
     * Sets if the ground station is using TCP for video.
     * @param useTCP true if using TCP for video, false if using UDP for video.
     */
    public void setUseTCPVideo(boolean useTCPVideo) {
    	this.useTCPVideo = useTCPVideo;
    }
    
    /**
     * Gets the default IP address for the robot.
     * @return default IP address.
     */
    public String getDefaultIP() {
    	return defaultIP;
    }
    
    /**
     * Gets the world model (data used to paint background images on map
     * @return worldModel;
     */
    public WorldModel getWorldModel() {
    	return world;
    }
    
    /**
     * Sets the world model.
     * @param world the new world model.
     */
    private void setWorldModel(WorldModel world) {
        this.world = world;
        fireGroundStationEvent(GroundStationEvent.WORLD_MODEL_SET);
    }
    
    /**
     * Gets the default command port for the robot.
     * @return port.
     */
    public int getDefaultPort() {
    	return CommandSender.DEFAULT_PORT;
    }
    
    /**
     * Shuts down the ground station.
     */
    public void shutdown() {
        inputPoller.setRun(false);
        
        try {
            commandSender.disconnectUDP();
            serviceComm.disconnect();
            listeners.clear();
        }
        catch (IOException e) {
            System.err.println("Error disconnecting UDP: " + e.getMessage());
        }
    }
    
    /**
     * Adds a ground station listener.
     * @param listener the ground station listener.
     */
    public void addGroundStationListener(GroundStationListener listener) {
        if ((listener != null) && !listeners.contains(listener)) listeners.add(listener);
    }
    
    /**
     * Removes a ground station listener.
     * @param listener the ground station listener.
     */
    public void removeGroundStationListener(GroundStationListener listener) {
        if (listeners.contains(listener)) listeners.remove(listener);
    }
    
    /**
     * Fires a ground station event of a given type.
     * @param eventType the event type.
     */
    public final void fireGroundStationEvent(String eventType) {
        fireGroundStationEvent(eventType, null);   
    }
    
    /**
     * Fires a ground station event of a given type and target object.
     * @param eventType the event type.
     * @param target the target object.
     */
    public final void fireGroundStationEvent(String eventType, Object target) {
        GroundStationEvent event = new GroundStationEvent(this, eventType, target);
        Iterator<GroundStationListener> i = listeners.iterator();
        while (i.hasNext()) {
            i.next().groundStationUpdate(event);
        }
    }
    
    /**
     * Main method for running ground station.
     * @param args string arguments.
     */
    public static void main(String[] args) {
        
        // Getting default IP address and imagery path from command args if there.
        String defaultIP = (args.length > 0) ? args[0] : "0.0.0.0";
        String worldPath = (args.length > 1) ? args[1] : "resources/metaData.txt";
        
        GroundStation gs = new GroundStation(defaultIP, worldPath);
        new GroundStationFrame(gs);
    }
}